#include "HttpSocket.h"
#include <stdio.h>
#include <string.h>

#ifdef WIN32
#include <winsock2.h>
#include <mswsock.h>
#include <Ws2tcpip.h>
#define OPTINT int
#define HttpGetError()  WSAGetLastError()
#define HTTPETIMEOUT    WSAETIMEDOUT
#define HTTPEWBLOCK	    WSAEWOULDBLOCK
#define SHUT_RECV  SD_RECEIVE
#define SHUT_SEND  SD_SEND
#define SHUT_BOTH  SD_BOTH
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#define OPTINT uint32_t
#define HttpGetError() errno
#define closesocket(s) close(s)
#define ioctlsocket(s,cmd,arg) ioctl(s,cmd,arg)
#define HTTPETIMEOUT EAGAIN
#define HTTPEWBLOCK EINPROGRESS
#define SHUT_RECV  SHUT_RD
#define SHUT_SEND  SHUT_WR
#define SHUT_BOTH  SHUT_RDWR
#endif

#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>

/*according to openssl manual
SSL_CTX (SSL Context)
That's the global context structure which is created 
by a server or client once per program life-time and 
which holds mainly default values for the SSL structures 
which are later created for the connections.
*/

static SSL_CTX *SSLInitialize();
static SSL_CTX *s_sslctx = NULL;//SSLInitialize();

static SSL_CTX *SSLInitialize()
{
	SSL_CTX *ctx = 0;
	SSL_library_init();
	OpenSSL_add_all_algorithms();
	ctx = SSL_CTX_new(SSLv23_client_method());
	RAND_poll();
	while (RAND_status() == 0){
		unsigned short rand_ret = rand() % 65536;
		RAND_seed(&rand_ret, sizeof(rand_ret));
	}
	return ctx;
}

HttpSocket::HttpSocket()
{	
	m_socket = -1;
	m_peer.addr = 0;
	m_peer.port = 0;
	m_connected = false;

	m_rdtimeo = 6000;
	m_wrtimeo = 6000;
	m_conntimeo = 6000;
	
	m_ssl = 0;
	m_usessl = false;
}


int HttpSocket::SetPeer(uint32_t addr, uint16_t port, bool ssl)
{
	Disconnect();

	m_peer.addr = addr;
	m_peer.port = port;
	m_usessl = ssl;
	if(ssl && !s_sslctx) {
		s_sslctx = SSLInitialize();
	}
	return 0;
}


int HttpSocket::SetTimeout(int recvtime, int sendtime, int conntime)
{
	if(conntime > 0)
		m_conntimeo = conntime;

	if(recvtime > 0)
		m_rdtimeo = recvtime;

	if(sendtime > 0)
		m_wrtimeo = sendtime;

	return 0;
}


int HttpSocket::DoSetTimeout()
{
	int retval = -1;

#ifdef WIN32
	retval = setsockopt(m_socket,SOL_SOCKET,\
		SO_RCVTIMEO,(char*)&m_rdtimeo,sizeof(m_rdtimeo));

	retval = setsockopt(m_socket,SOL_SOCKET,\
		SO_SNDTIMEO,(char*)&m_wrtimeo,sizeof(m_wrtimeo));
#else //LINUX
	struct timeval tv;
	tv.tv_sec = m_rdtimeo/1000;
	tv.tv_usec = (m_rdtimeo - 1000 * tv.tv_sec)*1000;
	retval = setsockopt(m_socket,SOL_SOCKET,\
		SO_RCVTIMEO,(char*)&tv,sizeof(tv));

	tv.tv_sec = m_wrtimeo/1000;
	tv.tv_usec = (m_wrtimeo - 1000 * tv.tv_sec)*1000;
	retval = setsockopt(m_socket,SOL_SOCKET,\
		SO_SNDTIMEO,(char*)&tv,sizeof(tv));
#endif

	return 0;
}


int HttpSocket::NewSession()
{
	Disconnect();

	m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	
	if(m_socket == -1) 
		return -1;

	DoSetTimeout();

	if(m_usessl) {
		m_ssl = SSL_new(s_sslctx);
		if(m_ssl == 0)
			return -1;
		if(0 == SSL_set_fd(m_ssl, m_socket)) {
			SSL_free(m_ssl); 
			m_ssl = 0;
			return -1;
		}
	}

	return 0;
}


int HttpSocket::Connect()
{
	if(m_connected)
		return 0;
	
	int retval = NewSession();
	if(retval == 0) {
		retval = DoConnect();
		if(m_usessl && retval==0) {
			if(1 == SSL_connect(m_ssl))
				retval = 0;
		}
	}

	if(retval == 0) {
		m_connected = true;
		return 0;
	}

	return -1;
}


int HttpSocket::DoConnect()
{
	int retval = -1;
	
	unsigned long nbopt = 1; 
	retval = ioctlsocket(m_socket, FIONBIO, &nbopt); 

	struct sockaddr_in siaddr;
	memset(&siaddr, 0, sizeof(siaddr));

	siaddr.sin_family = AF_INET;
	siaddr.sin_port = htons(m_peer.port);
	siaddr.sin_addr.s_addr = htonl(m_peer.addr);

	retval = connect(m_socket, (sockaddr *)&siaddr, sizeof(siaddr));

	int errcode = HttpGetError();
	
	if(retval==-1 && errcode==HTTPEWBLOCK) {
		fd_set fdwrite;
		FD_ZERO(&fdwrite);   
		FD_SET(m_socket, &fdwrite); 

		struct timeval tv;  
		tv.tv_sec = m_conntimeo/1000;   
		tv.tv_usec = (m_conntimeo - 1000 * tv.tv_sec)*1000;

		retval = select((int)m_socket+1, NULL, &fdwrite, NULL, &tv);   
		
		if(retval > 0) {//reset to blocking socket
			int err=-1;
            OPTINT len = sizeof(err);   

			//confirm connection is successful
			retval = getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char *)&err, &len);    
	
			if(err != 0) {     
				retval = -1;   
			} else {
				retval = 0;
			}

		} else {//select error or time out
			retval = -1;
		}
	}//if connect

	//reset to blocking socket
	if(retval == 0) {
		nbopt = 0;
		ioctlsocket(m_socket, FIONBIO, &nbopt); 
	}

	return retval;
}


/*Read:
  RETURN VALUES: @>=0,number of bytes read
                 @<0 ,error occured
*/
int HttpSocket::Read(char *pDataBuf, int nBufLen)
{
	int nResult = -1;
	int nTotalRead = 0;
	
	while(nBufLen > 0) {
		nResult = recv(m_socket, pDataBuf+nTotalRead, nBufLen, 0);
		if(nResult==-1 || nResult==0)
			break;
		nTotalRead += nResult;
		nBufLen -= nResult;
	}

	if(nResult == 0) { //connection is closed
		m_connected = false;
	} else if(nResult==-1 && HTTPETIMEOUT!=HttpGetError()) {
		m_connected = false;
		if(nTotalRead == 0)
			nTotalRead = -1;
	}

	return nTotalRead;
}


/*ReadWithSSL:
  RETURN VALUES: @>=0,number of bytes read
                 @<0 ,error occured
*/
int HttpSocket::ReadWithSSL(char *pDataBuf,int nBufLen)
{
	int nTotalRead = 0;
	int nResult = -1;
	while(nBufLen > 0) {
		nResult = SSL_read(m_ssl, pDataBuf+nTotalRead, nBufLen);
		if(nResult <= 0) 
			break;
		nTotalRead += nResult;
		nBufLen -= nResult;
	}
	
	//NETPRINT("  ==>sslread %d bytes,code=%d\n",nResult,HttpGetError());

	if(nResult <= 0) {//the connection is closed or an error occurred
		m_connected = false;
		if(nResult<0 && nTotalRead==0) {//error occurred
			nTotalRead = -1;
		}
	}

	return nTotalRead;
}


/*Write:
  RETURN VALUES: @>=0,number of bytes written
                 @<0 ,error occured
*/
int HttpSocket::Write(const char *pDataBuf, int nBufLen)
{
	if(Connect() != 0)
		return -1;

	int nResult = 0;
	int nTotalSent = 0;
	while(nBufLen > 0) {
		nResult = send(m_socket, pDataBuf+nTotalSent, nBufLen, 0);
		if(nResult == -1)
			break;
		nBufLen -= nResult;
		nTotalSent += nResult;
	}

	if(nResult == -1) {
		m_connected = false;
		if(nTotalSent == 0) 
			nTotalSent = -1;
	}

	return nTotalSent;
}


/*WriteWithSSL:
  RETURN VALUES: @>=0,number of bytes written
                 @<0 ,error occured
*/
int HttpSocket::WriteWithSSL(const char *pDataBuf, int nBufLen)
{
	if(Connect() != 0)
		return -1;

	int nResult = 0;
	int nTotalSent = 0;

	while(nBufLen > 0) {
		nResult = SSL_write(m_ssl, pDataBuf+nTotalSent, nBufLen);
		if(nResult <= 0)
			break;
		nBufLen -= nResult;
		nTotalSent += nResult;
	}

	if(nResult <= 0) {//connection is closed or an error occurred
		m_connected = false;
		if(nResult<0 && nTotalSent==0)
			nTotalSent = -1;
	}

	return nTotalSent;
}


int HttpSocket::SendRequest(const char *pReqHeader, int nHeaderLen,
							 const char *pReqData, int nDataLen)
{
	int nTotalSent = 0;
	int nBytesSent = 0;
	
	if(pReqHeader && nHeaderLen>0) {
		if(m_usessl) {
			nBytesSent = WriteWithSSL(pReqHeader, nHeaderLen);
		} else {
			nBytesSent = Write(pReqHeader, nHeaderLen);
		}
		if(nBytesSent > 0)
			nTotalSent += nBytesSent;
	}

	if(pReqData && nDataLen>0) {
		if(m_usessl) {
			nBytesSent = WriteWithSSL(pReqData,nDataLen);
		} else {
			nBytesSent = Write(pReqData,nDataLen);
		}
		if(nBytesSent > 0)
			nTotalSent += nBytesSent;
	}

	if(nBytesSent<0  &&  nTotalSent==0)
		nTotalSent = -1;

	return nTotalSent;
}

int HttpSocket::RecvResponse(char *pRcvDataBuf, int nRcvBufLen)
{
	if(!m_connected)
		return -1;
	
	int nTotalRead = 0;
	if(pRcvDataBuf && nRcvBufLen>0) {
		if(m_usessl) {
			nTotalRead = ReadWithSSL(pRcvDataBuf, nRcvBufLen);
		} else {
			nTotalRead = Read(pRcvDataBuf, nRcvBufLen);
		}
	}

	return nTotalRead;
}

int HttpSocket::RecvLine(char *pRcvDataBuf, int nRcvBufLen)
{
	if(!m_connected)
		return -1;

	int nTotalRead = 0;
	if(pRcvDataBuf && nRcvBufLen>0) {
		if(m_usessl) {
			nTotalRead = ReadLineSSL(pRcvDataBuf, nRcvBufLen);
		} else {
			nTotalRead = ReadLine(pRcvDataBuf, nRcvBufLen);
		}
	}

	return nTotalRead;
}

int HttpSocket::ReadLine(char *pDataBuf, int nBufLen)
{
	int nResult = -1;
	int nTotalRead = 0;

	while(nBufLen > 0) {
		nResult = recv(m_socket, pDataBuf+nTotalRead, 1, 0);
		if(nResult==-1 || nResult==0)
			break;
		nTotalRead += nResult;
		nBufLen -= nResult;
		if(pDataBuf[nTotalRead-nResult]=='\n') 
			break;
	}

	if(nResult == 0) { //connection is closed
		m_connected = false;
	} else if(nResult==-1 && HTTPETIMEOUT!=HttpGetError()) {
		m_connected = false;
		if(nTotalRead == 0)
			nTotalRead = -1;
	}

	return nTotalRead;
}

int HttpSocket::ReadLineSSL(char *pDataBuf, int nBufLen)
{
	int nTotalRead = 0;
	int nResult = -1;
	while(nBufLen > 0) {
		nResult = SSL_read(m_ssl, pDataBuf+nTotalRead, 1);
		if(nResult <= 0) 
			break;
		nTotalRead += nResult;
		nBufLen -= nResult;
		if(pDataBuf[nTotalRead-nResult]=='\n') 
			break;
	}

	//NETPRINT("  ==>sslread %d bytes,code=%d\n",nResult,HttpGetError());

	if(nResult <= 0) {//the connection is closed or an error occurred
		m_connected = false;
		if(nResult<0 && nTotalRead==0) {//error occurred
			nTotalRead = -1;
		}
	}

	return nTotalRead;
}

int HttpSocket::ReadProtoHeader(char *pDataBuf, int nBufLen)
{
	int nResult = -1;
	int nTotalRead = 0;

	int nLastLine = -3;
	while(nBufLen > 0) {
		nResult = recv(m_socket, pDataBuf+nTotalRead, 1, 0);
		if(nResult==-1 || nResult==0)
			break;
		int nOffSet = nTotalRead;
		nTotalRead += nResult;
		nBufLen -= nResult;
		if(pDataBuf[nOffSet] == '\n') {
			if(nOffSet-nLastLine <= 2) {
				if(nOffSet-nLastLine==1 || pDataBuf[nOffSet-1]=='\r')
					break;
			}
			nLastLine = nOffSet;
		}
	}

	if(nResult == 0) { //connection is closed
		m_connected = false;
	} else if(nResult==-1 && HTTPETIMEOUT!=HttpGetError()) {
		m_connected = false;
		if(nTotalRead == 0)
			nTotalRead = -1;
	}

	return nTotalRead;
}

int HttpSocket::ReadProtoHeaderSSL(char *pDataBuf, int nBufLen)
{
	int nTotalRead = 0;
	int nResult = -1;

	int nLastLine = -3;
	while(nBufLen > 0) {
		nResult = SSL_read(m_ssl, pDataBuf+nTotalRead, 1);
		if(nResult <= 0) 
			break;
		int nOffSet = nTotalRead;
		nTotalRead += nResult;
		nBufLen -= nResult;
		if(pDataBuf[nOffSet] == '\n') {
			if(nOffSet-nLastLine <= 2) {
				if(nOffSet-nLastLine==1 || pDataBuf[nOffSet-1]=='\r')
					break;
			}
			nLastLine = nOffSet;
		}
	}

	//NETPRINT("  ==>sslread %d bytes,code=%d\n",nResult,HttpGetError());

	if(nResult <= 0) {//the connection is closed or an error occurred
		m_connected = false;
		if(nResult<0 && nTotalRead==0) {//error occurred
			nTotalRead = -1;
		}
	}

	return nTotalRead;
}

int HttpSocket::RecvProtoHeader(char *pRcvDataBuf, int nRcvBufLen)
{
	if(!m_connected)
		return -1;
	int nTotalRead = 0;
	if(pRcvDataBuf && nRcvBufLen>0) {
		if(m_usessl) {
			nTotalRead = ReadProtoHeaderSSL(pRcvDataBuf, nRcvBufLen);
		} else {
			nTotalRead = ReadProtoHeader(pRcvDataBuf, nRcvBufLen);
		}
	}
	return nTotalRead;
}

int HttpSocket::Disconnect()
{
	if(m_socket != -1) {
		shutdown(m_socket, SHUT_BOTH);
		closesocket(m_socket);
		m_socket = -1;
	}
	
	if(m_ssl) {
		SSL_shutdown(m_ssl); 
		SSL_free(m_ssl); 
		m_ssl = 0;
	}

	m_connected = false;

	return 0;
}

HttpSocket::~HttpSocket()
{
	Disconnect();
}

// EOF
